#import <stdio.h>
#import <math.h>
#import <strings.h>
#import <stdlib.h>
#import <streams/streams.h>
#import <appkit/graphics.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/color.h>
#import "pgm.h"
#import "NXBitmapImageRepControl.h"
#import "ImageControl.h"

@implementation PGM

	NXStream		*myStream;
	unsigned char		*data;
	char				type[1000];
	int				width, height, levels;
	int				bits;
	int				cwid;
	int				iwid;
	int				padBits;

- init
{
	return self;
}

- free
{
	return self;
}

- killToEOL
{
	while (NXGetc(myStream) != '\n') ;
	return self;
}

- getString: (char *)str from: (NXStream *)stream
{
	char		buffer[256];
	
	do {
		NXScanf(stream, "%s", str);
		if (str[0] == '#') {
			NXScanf(stream, "%[^\n]", buffer); (void)NXGetc(stream);
		}
	} while (str[0] == '#');

	return self;
}

- (int)getIntFrom: (NXStream *)stream
{
	char		buffer[256];
	
	do {
		NXScanf(stream, "%s", buffer);
		if (buffer[0] == '#') {
			NXScanf(stream, "%[^\n]", buffer); (void)NXGetc(stream);
		}
		else break;
	} while (1);
	return(atoi(buffer));
}

- (BOOL)readRawFromStream
{
	int				x, y, shift, curByte;
	unsigned int		c;
	unsigned int		*dataII = (unsigned int *)data;

#ifdef DEBUG
	fprintf(stderr, "Okay, we're off and reading raw data\n");
#endif

	[self killToEOL];

	shift = 0;
	dataII[curByte = 0] = 0;
	for (x = 0; x < height; x++) {
		for (y = 0; y < width; y++) {
			c = [self getIntFrom: myStream] << (iwid - bits);
			if (NXAtEOS(myStream)) return NO;
			if (shift + bits == iwid) {
				dataII[curByte++] |= (c >> shift);
				shift = 0;
				dataII[curByte] = 0;
			}
			else if (shift + bits > iwid) {
				dataII[curByte++] |= (c >> shift);
				shift = shift + bits - iwid;
				dataII[curByte] = c << (bits - shift);
			}
			else {
				dataII[curByte] |= (c >> shift);
				shift += bits;
			}
		}
		shift += padBits;
	}

	return YES;
}

- (BOOL)readCompressedFromStream
{
	int				x, y, shift, curByte;
	unsigned char		c;

#ifdef DEBUG
	fprintf(stderr, "Okay, we're off and reading condensed data\n");
#endif

	[self killToEOL];

	shift = 0;
	data[curByte = 0] = 0;
	for (x = 0; x < height; x++) {
		for (y = 0; y < width; y++) {
			c = NXGetc(myStream) << cwid - bits;
			if (NXAtEOS(myStream)) return NO;
			if (shift + bits == cwid) {
				data[curByte++] |= (c >> shift);
				shift = 0;
				data[curByte] = 0;
			}
			else if (shift + bits > cwid) {
				data[curByte++] |= (c >> shift);
				shift = shift + bits - cwid;
				data[curByte] = c << (bits - shift);
			}
			else {
				data[curByte] |= (c >> shift);
				shift += bits;
			}
		}
		shift += padBits;
	}

	return YES;
}

- readFromStream: (NXStream *)stream from: sender;
{
	id		image;

#ifdef DEBUG
	fprintf(stderr, "Made it to read\n");
#endif

	myStream = stream;

	[self getString: type from: myStream];
	width = [self getIntFrom: myStream];
	height = [self getIntFrom: myStream];
	levels = [self getIntFrom: myStream];
//	NXScanf(myStream, "%s %d %d %d", type, &width, &height, &levels);
	bits = log(levels + 1) / log (2);
	cwid = sizeof(unsigned char) * 8;
	iwid = sizeof(unsigned int) * 8;
#ifdef DEBUG
	fprintf(stderr, "Type %s of dimensions %dx%d and levels %d or %d bits. intbits = %d, charbits = %d\n", type, width, height, levels + 1, bits, iwid, cwid);
#endif

	image = [[NXBitmapImageRep alloc] 	initData: NULL
										pixelsWide: width
										pixelsHigh: height
										bitsPerSample: bits
										samplesPerPixel: 1
										hasAlpha: NO
										isPlanar: NO
										colorSpace: NX_OneIsWhiteColorSpace
										bytesPerRow: 0
										bitsPerPixel: 0];
	if (!image) return nil;
	data = [image data]; 

	padBits = [image bytesPerRow] * 8 - bits * width;

	if (!strcmp(type, "P2")) {
		if (![self readRawFromStream]) {
			[image free];
			return nil;
		}
	}
	else if (!strcmp(type, "P5")) {
		if (![self readCompressedFromStream]) {
			[image free];
			return nil;
		}
	}
	else return nil;
	
	return image;
}

static BOOL WriteRaw(id image, NXStream *stream, id imageCon)
{
	int					w = [image pixelsWide],
						h = [image pixelsHigh],
						length = w * h;
	GetPixelNextFunc		myFunc;
	ToGrayFunc			GetColor;
	int					gray;
	float					maxColor = pow(2, (float)[image bitsPerSample]) - 1.0;
	int					x, y;

	NXPrintf(stream, "P2\n");
	NXPrintf(stream, "%d %d\n", w, h);
	NXPrintf(stream, "%d\n", (int)maxColor);
	[imageCon resetNext];
	myFunc = [imageCon getNextFunction];
	GetColor = [imageCon getToGrayFunction];
	for (x = y = 0; x < length; x++, y++) {
		gray = GetColor(myFunc())->values[0];
		NXPrintf(stream, "%3d ", gray);
		if (y == 16) {
			y = 0;
			NXPrintf(stream, "\n");
		}
	}

	return YES;
}

static BOOL WriteCompact(id image, NXStream *stream, id imageCon)
{
	int					w = [image pixelsWide],
						h = [image pixelsHigh],
						length = w * h;
	GetPixelNextFunc		myFunc;
	ToGrayFunc			GetGray;
	float					maxColor = pow(2, (float)[image bitsPerSample]) - 1.0;
	int					x;

	NXPrintf(stream, "P5\n");
	NXPrintf(stream, "%d %d\n", w, h);
	NXPrintf(stream, "%d\n", (int)maxColor);
	[imageCon resetNext];
	myFunc = [imageCon getNextFunction];
	GetGray = [imageCon getToGrayFunction];
	for (x = 0; x < length; x++) {
		NXPutc(stream, GetGray(myFunc())->values[0]);
	}

	return YES;
}

- (BOOL)write: (id)image toStream: (NXStream *)stream from: sender;
{
	id		imageCon = [sender getImageControl: image];

	if ([image bitsPerSample] > 8) {
		return WriteRaw(image, stream, imageCon);
	} else {
		return WriteCompact(image, stream, imageCon);
	}
}

- readAllFromStream: (NXStream *)stream from: sender
{
	return nil;
}

- (BOOL)writeAll: (id)image toStream: (NXStream *)stream
{
	return NO;
}

- customSaveView: (int)width
{
	return nil;
}

- customOpenView: (int)width
{
	return nil;
}

- (char *)getFormatName
{
	return("Portable Graymap (PGM)");
}

 - (BOOL)setCustomParameter: (const char *)parameter withValue: (void *)ptr
{
	return NO;
}

 - (void *)getCustomParameter: (const char *)parameter
{
	return nil;
}

- (char *)copyrightNotice
{
	return "PGM Converter\nby Alex Raftis\nCopyright (c) 1991 Cal Poly State University\nEmail bugs to alex@data.ACS.CalPoly.EDU";
}
 
- (int)errorState
{
	return CONVERT_ERR_NONE;
}
 
- (int)errorMessage
{
	return ERROR_NO_ERROR;
}

- (char *)errorStringMessage
{
	return NULL;
}
 
- (BOOL)needsWindowServer;
{
	return NO;
}

- (char *)protocolVersion
{
	return "1.0";
}

@end
